#include <windows.h>
#include <winuser.h>
#include <node_api.h>
#include <map>
#include <iostream>

#include "hander.h"

#ifndef __GlobalRecorderWin_deftag
#define __GlobalRecorderWin_deftag

namespace GlobalRecorderWin
{
    std::map<int, bool> keyMap;

    HHOOK keyboardHook = 0, mouseHook = 0;

    struct tagKBDLLHOOKSTRUCTFlags
    {
        /*
Bits	Description
0	    Specifies whether the key is an extended key, such as a function key or a key on the numeric keypad. The value is 1 if the key is an extended key; otherwise, it is 0.
1	    Specifies whether the event was injected from a process running at lower integrity level. The value is 1 if that is the case; otherwise, it is 0. Note that bit 4 is also set whenever bit 1 is set.
2-3	    Reserved.
4	    Specifies whether the event was injected. The value is 1 if that is the case; otherwise, it is 0. Note that bit 1 is not necessarily set when bit 4 is set.
5	    The context code. The value is 1 if the ALT key is pressed; otherwise, it is 0.
6	    Reserved.
7	    The transition state. The value is 0 if the key is pressed and 1 if it is being released.
         */
        tagKBDLLHOOKSTRUCTFlags(DWORD flags) : raw(flags)
        {
            this->isExtended = flags & 0b1;
            this->isInjected = (flags >> 1) & 0b1;
            this->reserved1 = (flags >> 2) & 0b11;
            this->isInjectedEvent = (flags >> 4) & 0b1;
            this->alt = (flags >> 5) & 0b1;
            this->reserved2 = (flags >> 6) & 0b11;
            this->transition = (flags >> 7) & 0b1;
        }
        DWORD raw;
        unsigned int isExtended : 1;
        unsigned int isInjected : 1;
        unsigned int reserved1 : 2;
        unsigned int isInjectedEvent : 1;
        unsigned int alt : 1;
        unsigned int reserved2 : 2;
        unsigned int transition : 1;
    };

    /** 调用 js-callback 用的 */

    LRESULT CALLBACK
    KeyboardProc(
        _In_ int nCode,
        _In_ WPARAM wParam,
        _In_ LPARAM lParam)
    {

        auto ks = reinterpret_cast<KBDLLHOOKSTRUCT *>(lParam);
        /*
        typedef struct tagKBDLLHOOKSTRUCT {
            DWORD     vkCode;		// A virtual-key code. The code must be a value in the range 1 to 254.
            DWORD     scanCode;		// A hardware scan code for the key.
            DWORD     flags;
            DWORD     time;
            ULONG_PTR dwExtraInfo;	// Additional information associated with the message.
        }KBDLLHOOKSTRUCT,*LPKBDLLHOOKSTRUCT,*PKBDLLHOOKSTRUCT;
        */
        auto flags = tagKBDLLHOOKSTRUCTFlags(ks->flags);
        // 分配内存空间
        auto m = new GlobalRecorderHandler::CallbackArgs(0, 0, 0, 0, 0);
        m->x = 0;
        m->y = 0;
        m->value = ks->vkCode;
        // 判断键盘事件类型
        if (flags.transition)
        {
            m->type = 0;
            m->action = 2;
            keyMap[ks->vkCode] = false;
        }
        else
        {
            if (keyMap.find(ks->vkCode) == keyMap.end() || !keyMap[ks->vkCode])
            {
                keyMap[ks->vkCode] = true;
                m->type = 0;
                m->action = 0;
            }
            else
            {
                m->type = 0;
                m->action = 1;
            }
        }
        GlobalRecorderHandler::catch_err(GlobalRecorderHandler::env, napi_call_threadsafe_function(
                                                                         GlobalRecorderHandler::addon->tsfn, // js-callback 函数
                                                                         m,                                  // call_js 的第四个参数
                                                                         napi_tsfn_nonblocking));

        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }
    std::map<int, int> MouseButtonIds = {
        {WM_MOUSEMOVE, 0},
        {WM_LBUTTONDOWN, 1},
        {WM_LBUTTONUP, 2},
        {WM_LBUTTONDBLCLK, 3},
        {WM_RBUTTONDOWN, 4},
        {WM_RBUTTONUP, 5},
        {WM_RBUTTONDBLCLK, 6},
        {WM_MBUTTONDOWN, 7},
        {WM_MBUTTONUP, 8},
        {WM_MBUTTONDBLCLK, 9},
        {WM_MOUSEWHEEL, 10}};
    ;
    LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
    {

        auto p = (MSLLHOOKSTRUCT *)lParam;
        POINT pt = p->pt;

        short whellValue = p->mouseData >> 16;

        auto m = new GlobalRecorderHandler::CallbackArgs(1, MouseButtonIds[wParam], whellValue, pt.x, pt.y);
        GlobalRecorderHandler::catch_err(GlobalRecorderHandler::env, napi_call_threadsafe_function(
                                                                         GlobalRecorderHandler::addon->tsfn, // js-callback 函数
                                                                         m,                                  // call_js 的第四个参数
                                                                         napi_tsfn_nonblocking));

        return CallNextHookEx(NULL, nCode, wParam, lParam);
    }
    /** 执行线程 */
    static void execute_work(napi_env now_env, void *data)
    {
        GlobalRecorderHandler::addon = static_cast<GlobalRecorderHandler::Addon *>(data);

        GlobalRecorderHandler::catch_err(GlobalRecorderHandler::env, napi_acquire_threadsafe_function(GlobalRecorderHandler::addon->tsfn));

        keyboardHook = SetWindowsHookEx(
            WH_KEYBOARD_LL,
            KeyboardProc,
            GetModuleHandleA(NULL),
            NULL);
        mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, GetModuleHandleA(NULL), NULL);

        // Message loop
        MSG msg;
        while (1)
        {
            // 如果消息队列中有消息
            if (PeekMessageA(
                    &msg,
                    NULL,
                    NULL,
                    NULL,
                    PM_REMOVE // after processing, delete it
                    ))
            {
                TranslateMessage(&msg);

                DispatchMessageW(&msg);
            }
            else
                Sleep(0);
        }
        UnhookWindowsHookEx(keyboardHook);
        UnhookWindowsHookEx(mouseHook);

        GlobalRecorderHandler::catch_err(GlobalRecorderHandler::env, napi_release_threadsafe_function(GlobalRecorderHandler::addon->tsfn, napi_tsfn_release));
    }

    /** 线程执行完成 */
    static void work_complete(napi_env env, napi_status status, void *data)
    {
        GlobalRecorderHandler::Addon *addon = (GlobalRecorderHandler::Addon *)data;

        // 释放句柄
        GlobalRecorderHandler::catch_err(env, napi_release_threadsafe_function(addon->tsfn, napi_tsfn_release));

        // 回收任务
        GlobalRecorderHandler::catch_err(env, napi_delete_async_work(env, addon->work));

        addon->work = NULL;
        addon->tsfn = NULL;
    }

    static napi_value state(napi_env env, napi_callback_info info)
    {
        size_t argc = 1; // js 传进来的参数个数
        napi_value keyId;
        GlobalRecorderHandler::Addon *addon; // “实例化” 结构体 (个人理解是取出了传进来的 js-cabllback 地址指针，期待大神来讨论 ?)
        napi_status sts;                     // 程序执行状态

        sts = napi_get_cb_info(
            env,              // 执行上下文，可以理解为 js 那个 “事件环”
            info,             // 上下文信息
            &argc,            // 收到参数的个数
            &keyId,           // 接收 js 参数
            NULL,             // 接收 js 的 this 对象
            (void **)(&addon) // 取得 js 传进来的 callback 的指针地址
        );
        GlobalRecorderHandler::catch_err(env, sts);

        // 从 js 传进来的参数中取出 keyId

        int64_t key;
        napi_get_value_int64(env, keyId, &key);
        auto res = GetKeyState(key);
        auto ks = std::make_unique<GlobalRecorderHandler::KeyState>(res >> 15, res & 1);
        return GlobalRecorderHandler::KeyState::toObj(env, std::move(ks));
    }
    napi_value init(napi_env env, napi_value exports)
    {
        auto localInfo = GlobalRecorderHandler::ModuleLocalInfo(state, work_complete, execute_work, "win-api");
        return GlobalRecorderHandler::createModule(env, exports, std::move(localInfo));
    }

    NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
}

#endif